import tkinter as tk
from tkinter import messagebox, filedialog, simpledialog
import os
import json
import base64
import hashlib

class LineNumberCanvas(tk.Canvas):
    def __init__(self, *args, **kwargs):
        self.text_widget = None
        super().__init__(*args, **kwargs)
        self.config(width=45, bg='#f0f0f0')

    def attach(self, text_widget):
        self.text_widget = text_widget
        self.redraw()

    def redraw(self, *args):
        self.delete("all")

        if not self.text_widget:
            return

        first_line = self.text_widget.index("@0,0")
        last_line = self.text_widget.index("@0,%d" % self.winfo_height())

        first = int(float(first_line))
        last = int(float(last_line)) + 1

        fill_color = 'gray'
        font = ('Consolas', 10)

        for line_num in range(first, last):
            dline = self.text_widget.dlineinfo("%d.0" % line_num)
            if dline:
                y = dline[1]
                self.create_text(40, y, anchor="ne",
                               text=str(line_num),
                               font=font,
                               fill=fill_color)

    def _on_text_modified(self, event=None):
        self.redraw()

    def _on_scroll(self, *args):
        self.redraw()

class SearchBar(tk.Frame):
    def __init__(self, master, text_widget, status_bar, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.text_widget = text_widget
        self.status_bar = status_bar
        self.config(bg='#f0f0f0', padx=5, pady=5)

        # 搜索输入框
        tk.Label(self, text="查找:", bg='#f0f0f0').pack(side=tk.LEFT, padx=2)
        self.search_var = tk.StringVar()
        self.search_entry = tk.Entry(self, textvariable=self.search_var)
        self.search_entry.pack(side=tk.LEFT, padx=5)
        # 敲击回车默认查找下一个
        self.search_entry.bind('<Return>', lambda e: self.find_next())

        # 替换输入框
        tk.Label(self, text="替换:", bg='#f0f0f0').pack(side=tk.LEFT, padx=2)
        self.replace_var = tk.StringVar()
        self.replace_entry = tk.Entry(self, textvariable=self.replace_var)
        self.replace_entry.pack(side=tk.LEFT, padx=5)

        # 按钮
        tk.Button(self, text="关闭", command=self.hide).pack(side=tk.RIGHT, padx=2)
        tk.Button(self, text="全部替换", command=self.replace_all).pack(side=tk.RIGHT, padx=2)
        tk.Button(self, text="替换", command=self.replace).pack(side=tk.RIGHT, padx=2)
        tk.Button(self, text="查找下一个", command=self.find_next).pack(side=tk.RIGHT, padx=2)
        tk.Button(self, text="查找上一个", command=self.find_prev).pack(side=tk.RIGHT, padx=2)
        tk.Button(self, text="计数", command=self.count_matches).pack(side=tk.RIGHT, padx=2)

        self.pack_forget()

    def show(self):
        self.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2)
        self.search_entry.focus_set()

    def hide(self):
        self.pack_forget()
        self.search_var.set("")      # 清空搜索输入框
        self.replace_var.set("")     # 清空替换输入框
        self.text_widget.tag_remove('search', '1.0', tk.END)
        self.text_widget.focus_set()

    def count_matches(self):
        search_text = self.search_var.get()
        if search_text:
            content = self.text_widget.get('1.0', tk.END)
            count = content.count(search_text)
            self.status_bar.show_message(f"符合条件的，共计 {count} 个")
        else:
            self.status_bar.show_message("请输入查找内容")

    def get_all_match_positions(self, search_text):
        """返回所有匹配项的起始位置列表"""
        positions = []
        if not search_text:
            return positions
        start = '1.0'
        while True:
            pos = self.text_widget.search(search_text, start, tk.END)
            if not pos or pos == '':
                break
            positions.append(pos)
            # 跳过当前找到的内容，避免死循环
            line, char = pos.split('.')
            next_index = f"{line}.{int(char) + 1}"
            start = next_index
        return positions

    def find_prev(self):
        self.text_widget.tag_remove('search', '1.0', tk.END)
        search_text = self.search_var.get()
        if search_text:
            positions = self.get_all_match_positions(search_text)
            if not positions:
                self.status_bar.show_message("未找到匹配项")
                return

            cur_index = self.text_widget.index(tk.INSERT)
            prev_pos = None
            for i, pos in enumerate(positions):
                if self.text_widget.compare(pos, '>=', cur_index):
                    prev_pos = positions[i-1] if i > 0 else positions[-1]
                    break
            else:
                prev_pos = positions[-1]

            # 计算当前是第几个
            idx = positions.index(prev_pos) + 1
            total = len(positions)
            self.status_bar.show_message(f"当前第 {idx} 个，共计 {total} 个")

            line, char = prev_pos.split('.')
            end_pos = f"{line}.{int(char) + len(search_text)}"
            self.text_widget.tag_add('search', prev_pos, end_pos)
            self.text_widget.tag_config('search', background='red')
            self.text_widget.see(prev_pos)
            self.text_widget.mark_set(tk.INSERT, prev_pos)

    def find_next(self):
        self.text_widget.tag_remove('search', '1.0', tk.END)
        search_text = self.search_var.get()
        if search_text:
            positions = self.get_all_match_positions(search_text)
            if not positions:
                self.status_bar.show_message("未找到匹配项")
                return

            cur_index = self.text_widget.index(tk.INSERT)
            next_pos = None
            for i, pos in enumerate(positions):
                if self.text_widget.compare(pos, '>', cur_index):
                    next_pos = pos
                    break
            if not next_pos:
                next_pos = positions[0]

            idx = positions.index(next_pos) + 1
            total = len(positions)
            self.status_bar.show_message(f"当前第 {idx} 个，共计 {total} 个")

            line, char = next_pos.split('.')
            end_pos = f"{line}.{int(char) + len(search_text)}"
            self.text_widget.tag_add('search', next_pos, end_pos)
            self.text_widget.tag_config('search', background='red')
            self.text_widget.see(next_pos)
            self.text_widget.mark_set(tk.INSERT, end_pos)

    def replace(self):
        search_text = self.search_var.get()
        replace_text = self.replace_var.get()

        if search_text:
            try:
                start = self.text_widget.tag_ranges('search')[0]
                end = self.text_widget.tag_ranges('search')[1]
                self.text_widget.delete(start, end)
                self.text_widget.insert(start, replace_text)
                self.find_next()
            except IndexError:
                self.find_next()

    def replace_all(self):
        search_text = self.search_var.get()
        replace_text = self.replace_var.get()

        if search_text:
            content = self.text_widget.get('1.0', tk.END)
            new_content = content.replace(search_text, replace_text)
            self.text_widget.delete('1.0', tk.END)
            self.text_widget.insert('1.0', new_content)

class StatusBar(tk.Frame):
    def __init__(self, master, text_widget, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.text_widget = text_widget
        self.config(bg='#f0f0f0', padx=5, pady=2)

        # 创建状态标签
        self.position_label = tk.Label(self, text="行: 1 列: 1 长度: 0", bg='#f0f0f0')
        self.position_label.pack(side=tk.LEFT, padx=5)

        # 绑定文本更改事件
        self.text_widget.bind('<<Modified>>', self.update_status)
        self.text_widget.bind('<FocusIn>', self.update_status)
        self.text_widget.bind('<ButtonRelease-1>', self.update_status)
        self.text_widget.bind('<KeyRelease>', self.update_status)
        self.text_widget.bind('<Motion>', self.update_status)  # 鼠标移动
        self.text_widget.bind('<ButtonRelease-2>', self.update_status)  # 鼠标中键
        self.text_widget.bind('<ButtonRelease-3>', self.update_status)  # 鼠标右键

    def update_status(self, event=None):
        self.after_idle(self._really_update_status)

    def _really_update_status(self):
        try:
            pos = self.text_widget.index(tk.INSERT)
            line, col = map(int, pos.split('.'))
            content = self.text_widget.get('1.0', tk.END)
            length = len(content) - 1
            self.position_label.config(text=f"行: {line} 列: {col + 1} 长度: {length}")
        except Exception:
            self.position_label.config(text="行: - 列: - 长度: -")

    def show_message(self, msg):
        self.position_label.config(text=msg)
        self.after(5000, self.update_status)  # 5秒后恢复原状态

class TextEditor:
    def __init__(self, root):
        self.global_password = None  # 全局密码
        self.undo_stack_size = 1000
        self.compound_undo_delay = 1000

        self.root = root
        self.root.title("文本编辑器")
        self.current_file = None
        self.config_file = os.path.join(os.path.expanduser('~'), '.noteedit_config.json')
        self.load_config()

        # 创建主框架
        self.main_frame = tk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建菜单
        self.create_menu()
        
        # 创建水平布局框架
        self.h_frame = tk.Frame(self.main_frame, bg='white')
        self.h_frame.pack(fill=tk.BOTH, expand=True)

        # 行号画布
        self.line_numbers = LineNumberCanvas(self.h_frame, bg='#f0f0f0')
        self.line_numbers.pack(side=tk.LEFT, fill=tk.Y)

        # 文本编辑区（不自动换行）
        self.text_area = tk.Text(self.h_frame, wrap=tk.NONE, font=('Consolas', 11),
                          bg='white', fg='black', insertbackground='black',
                          undo=True, maxundo=self.undo_stack_size, autoseparators=True)
        self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 垂直滚动条
        self.text_scrollbar = tk.Scrollbar(self.h_frame, orient="vertical",
                                    command=self.text_area.yview)
        self.text_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.text_area.config(yscrollcommand=self.text_scrollbar.set)

        # 横向滚动条
        self.text_scrollbar_x = tk.Scrollbar(self.main_frame, orient="horizontal",
                                             command=self.text_area.xview)
        self.text_scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
        self.text_area.config(xscrollcommand=self.text_scrollbar_x.set)

        # 状态栏
        self.status_bar = StatusBar(self.main_frame, self.text_area)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 创建搜索栏
        self.search_bar = SearchBar(self.main_frame, None, self.status_bar)

        # 更新搜索栏
        self.search_bar.text_widget = self.text_area

        # 绑定事件
        self.text_area.bind('<<Modified>>', self.on_text_modified)
        self.text_area.bind('<Configure>', self.line_numbers.redraw)
        self.text_area.bind('<KeyPress>', self.line_numbers.redraw)
        self.text_area.bind('<KeyRelease>', self.line_numbers.redraw)
        self.text_area.bind('<MouseWheel>', self.line_numbers._on_scroll)

        # 初始化行号
        self.line_numbers.attach(self.text_area)
        self.line_numbers.redraw()

        # 绑定快捷键
        self.text_area.bind('<Control-f>', lambda e: self.search_bar.show())
        self.text_area.bind('<Escape>', lambda e: self.search_bar.hide())
        self.text_area.bind('<Control-s>', lambda e: self.handle_save(e))
        self.text_area.bind("<Control-S>", lambda e: self.save_file_as())  # Ctrl+Shift+S
        self.text_area.bind('<Control-z>', lambda e: self.handle_undo(e))
        self.text_area.bind('<Control-o>', lambda e: self.handle_open_file(e))
        self.text_area.bind("<Control-p>", lambda e: self.set_global_password())  # Ctrl+P
        self.text_area.bind("<Control-q>", lambda e: self.exit_app()) # Ctrl+q
        self.text_area.bind('<Control-Shift-Z>', lambda e: self.handle_redo(e))  # Ctrl+Shift+Z

        # 绑定关闭窗口事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def load_config(self):
        self.config = {'last_directory': os.getcwd()}
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    saved_config = json.load(f)
                    self.config.update(saved_config)

                if os.path.exists(self.config['last_directory']):
                    os.chdir(self.config['last_directory'])
        except Exception as e:
            print(f"加载配置文件失败: {str(e)}")

    def save_config(self):
        try:
            self.config['last_directory'] = os.getcwd()
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(f"保存配置文件失败: {str(e)}")

    def on_closing(self):
        if self.has_unsaved_changes():
            if not self.ask_save_changes():
                return

        self.save_config()
        self.root.destroy()

    def create_menu(self):
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)

        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="新建", command=self.new_file)
        file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
        file_menu.add_separator()
        file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
        file_menu.add_command(label="另存为", command=self.save_file_as, accelerator="Ctrl+Shift+S")
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.exit_app, accelerator="Ctrl+q")

        # 编辑菜单
        edit_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        edit_menu.add_command(label="设置全局密码", command=self.set_global_password, accelerator="Ctrl+P")
        edit_menu.add_separator()
        edit_menu.add_command(label="撤销", command=self.handle_undo, accelerator="Ctrl+Z")
        edit_menu.add_command(label="恢复", command=self.handle_redo, accelerator="Ctrl+Shift+Z")
        edit_menu.add_command(label="查找", command=lambda: self.search_bar.show(), accelerator="Ctrl+F")

        # 帮助菜单
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="帮助", menu=help_menu)
        help_menu.add_command(label="版本信息", command=self.show_version_info)

    def show_version_info(self):
        messagebox.showinfo("版本信息", "文本编辑器 v1.0.0\n作者：王二麻子")

    def set_global_password(self):
        """弹出输入框，设置全局密码"""
        password = simpledialog.askstring("PWD", "请输入全局密码：", show="*")
        if password is not None:  # 用户点击了取消则返回 None
            self.global_password = password
            self.status_bar.show_message("全局密码已设置")

    def open_file(self, file_path=None):
        if not file_path:
            file_path = filedialog.askopenfilename(
                filetypes=[("所有文件", "*.*"), ("文本文件", "*.txt"), ("PRI文件", "*.pri")]
            )

        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()

                # 如果是.pri文件并且全局密码已设置，则逐行解密
                if file_path.endswith('.pri') and self.global_password:
                    decrypted_lines = []
                    for line in content.splitlines():
                        try:
                            # 尝试解密
                            decrypted_line = self.decrypt_string(line, self.global_password)
                        except Exception:
                            # 解密失败（可能是密码错误或者该行不是有效的base64），则保留原行
                            decrypted_line = line
                        decrypted_lines.append(decrypted_line)
                    content = '\n'.join(decrypted_lines)

                # 设置文本内容
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(1.0, content)

                # 保存文件路径
                self.current_file = file_path
                self.root.title(f"文本编辑器 - {os.path.basename(file_path)}")

            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件: {str(e)}")

    def save_file(self):
        if self.current_file:
            try:
                content = self.text_area.get(1.0, tk.END)[:-1]  # 去掉最后一个换行符

                # 如果是.pri文件并且全局密码已设置，则逐行加密
                if self.current_file.endswith('.pri') and self.global_password:
                    encrypted_lines = []
                    for line in content.splitlines():
                        encrypted_line = self.encrypt_string(line, self.global_password)
                        encrypted_lines.append(encrypted_line)
                    content = '\n'.join(encrypted_lines)

                with open(self.current_file, 'w', encoding='utf-8') as file:
                    file.write(content)
                
                # 更新标题
                self.root.title(f"文本编辑器 - {os.path.basename(self.current_file)}")
                
            except Exception as e:
                messagebox.showerror("错误", f"无法保存文件: {str(e)}")
        else:
            self.save_file_as()

    def save_file_as(self):
        file_path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("PRI文件", "*.pri"), ("所有文件", "*.*")]
        )
        if file_path:
            self.current_file = file_path
            self.save_file()

    def new_file(self):
        self.text_area.delete(1.0, tk.END)
        self.current_file = None
        self.root.title("文本编辑器")

    def has_unsaved_changes(self):
        if self.current_file:
            try:
                with open(self.current_file, 'r', encoding='utf-8') as file:
                    saved_content = file.read()
                current_content = self.text_area.get(1.0, tk.END)[:-1]
                return saved_content != current_content
            except:
                return True
        else:
            return len(self.text_area.get(1.0, tk.END).strip()) > 0

    def ask_save_changes(self):
        answer = messagebox.askyesnocancel(
            "保存更改",
            "当前文件有未保存的更改，是否保存？"
        )
        if answer is None:
            return False
        elif answer:
            self.save_file()
        return True

    def on_text_modified(self, event):
        self.text_area.edit_modified(False)

        if self.current_file:
            current_title = self.root.title()
            if not current_title.startswith("*"):
                if self.has_unsaved_changes():
                    self.root.title(f"*{current_title}")

    def handle_save(self, event):
        self.save_file()
        return "break"

    def handle_undo(self, event=None):
        try:
            self.text_area.edit_undo()
        except tk.TclError:
            pass
        if event:
            return "break"
        
    def handle_redo(self, event=None):
        try:
            self.text_area.edit_redo()
        except tk.TclError:
            pass
        if event:
            return "break"

    def handle_open_file(self, event):
        self.open_file()
        return "break"

    def encrypt_string(self, plain_text: str, key: str) -> str:
        """简单加密字符串（仅依赖标准库）"""
        key_bytes = hashlib.sha256(key.encode()).digest()
        plain_bytes = plain_text.encode()
        enc_bytes = bytearray()
        for i in range(len(plain_bytes)):
            enc_bytes.append(plain_bytes[i] ^ key_bytes[i % len(key_bytes)])
        return base64.urlsafe_b64encode(enc_bytes).decode()

    def decrypt_string(self, cipher_text: str, key: str) -> str:
        """简单解密字符串（仅依赖标准库）"""
        key_bytes = hashlib.sha256(key.encode()).digest()
        enc_bytes = base64.urlsafe_b64decode(cipher_text.encode())
        plain_bytes = bytearray()
        for i in range(len(enc_bytes)):
            plain_bytes.append(enc_bytes[i] ^ key_bytes[i % len(key_bytes)])
        return plain_bytes.decode()

    def exit_app(self):
        """退出应用程序"""
        self.root.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    editor = TextEditor(root)
    root.geometry("800x600")
    root.mainloop()